/******************************************************************************
 * Copyright (c) KylinSoft  Co., Ltd. 2021. All rights reserved.
 * lcr licensed under the Mulan PSL v2.

 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v2 for more details.
 * Author: xiapin
 * Create: 2021-12-24
 * Description: provide memory block operations.
 ******************************************************************************/
#define _GNU_SOURCE
#include "mem_block.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <dlfcn.h>

extern void *__libc_malloc(size_t size);
extern void __libc_free(void *ptr);
extern void *__libc_realloc(void *prev, size_t size);

static size_t (*dl_malloc_usable_size)(void *ptr);

/**
 * @description: Invoke clib free.
 * @param {void} *ptr
 * @return {*}
 */
void clib_free(void *ptr)
{
    __libc_free(ptr);
}

/**
 * @description: Invoke clib malloc.
 * @param {size_t} size
 * @return {*}
 */
void *clib_malloc(size_t size)
{
    return __libc_malloc(size);
}

void *clib_calloc(size_t nmemb, size_t size)
{
    void *p = __libc_malloc(nmemb * size);
    memset(p, 0, nmemb * size);

    return p;
}

/**
 * @description: Invoke clib realloc.
 * @param {void} *ptr
 * @param {size_t} size
 * @return {*}
 */
void *clib_realloc(void *ptr, size_t size)
{
    return __libc_realloc(ptr, size);
}

/**
 * @description: Invoke clib malloc_usable_size.
 * @param {void *} The pointer to calculate.
 * @return {size_t} Available size of pointer.
 */
size_t clib_malloc_usable_size(void *ptr)
{
    if (dl_malloc_usable_size != NULL) {
        return dl_malloc_usable_size(ptr);
    }

    dl_malloc_usable_size = dlsym(RTLD_NEXT, "malloc_usable_size");
    return dl_malloc_usable_size(ptr);
}

/**
 * @description: Get memory from clib, if memory pool exhausted.
 * @param {size_t} size
 * @return {*}
 */
void *get_mem_from_clib(size_t size)
{
    mem_unit_t *p = (mem_unit_t *)clib_malloc(sizeof(mem_unit_t) + size);
    p->info.belongs = 0;
    p->info.unit_size = size;
    p->check = MU_CHECK_VALUE(p);

    return mem_unit_to_ptr(p);
}

/**
 * @description: Initialization a memory unit.
 * @param {mem_unit_t} *mu
 * @param {size_t} unit_size
 * @return {*}
 */
static __inline__ void mem_unit_init(mem_unit_t *mu, size_t unit_size)
{
    MP_ASSERT(mu != NULL);

    mu->info.belongs = 1;
    mu->info.unit_size = unit_size;
    mu->check = MU_CHECK_VALUE(mu);
    mu->next = NULL;
}

static struct st_chunk *alloc_new_chunk()
{
    struct st_chunk *chunk = clib_malloc(sizeof(struct st_chunk));

    MP_ASSERT(chunk != NULL);
    memset(chunk, 0, sizeof(sizeof(struct st_chunk)));
    chunk->buffer = mmap(0, PER_CACHE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    chunk->buffer_size = PER_CACHE_SIZE;
    return chunk;
}

void *get_mem_from_block(thread_cache_t *tcache, mem_block_info_t *mem_block, size_t unit_size)
{
    mem_unit_t *mu;
    struct list_head *pos;
    struct st_chunk *chunk;
    int real_size = unit_size + sizeof(mem_unit_t);

    /* from free list */
    if (mem_block->free_list != NULL) {
        mu = mem_block->free_list;
        mem_block->free_list = (mem_unit_t *)mem_block->free_list->next;

        return mem_unit_to_ptr(mu);
    }

    if (list_empty(tcache->chunk_list)) {
        goto NEW_CHUNK;
    }

    /* available chunk */
    list_for_each(pos, tcache->chunk_list) {
        chunk = list_entry(pos, struct st_chunk, list);
        if (chunk->offset + real_size <= chunk->buffer_size) {
            goto ALLOC_FROM_CHUNK;
        }
    }

NEW_CHUNK:
    chunk = alloc_new_chunk();
    list_add(&chunk->list, tcache->chunk_list);
ALLOC_FROM_CHUNK:
    mu = (mem_unit_t *)((uintptr_t)chunk->buffer + chunk->offset);
    chunk->offset += real_size;
    mem_unit_init(mu, unit_size);
    mem_block->alloced += unit_size;

    return mem_unit_to_ptr(mu);
}

S32 get_block_status(mem_block_info_t *blk, size_t *alloced, size_t *used)
{
    if (blk == NULL || alloced == NULL || used == NULL || blk->free_list == NULL) {
        return -1;
    }

    size_t free_size = 0;
    mem_unit_t *mu = blk->free_list;

    while (mu != NULL) {
        free_size += mu->info.unit_size;

        mu = mu->next;
    }

    *alloced = blk->alloced;
    *used = blk->alloced - free_size;
    return 0;
}